package collaboration

import (
	"fmt"
	"sync"
	"time"

	"github.com/google/uuid"
	"github.com/sirupsen/logrus"

	"xagent/internal/actors"
	proto "xagent/proto/v1"
)

// CollaborationType represents the type of collaboration
type CollaborationType string

const (
	CollaborationTypeTeam      CollaborationType = "team"
	CollaborationTypeWorkflow  CollaborationType = "workflow"
	CollaborationTypeConsensus CollaborationType = "consensus"
)

// Team represents a group of collaborating agents
type Team struct {
	ID          string
	Name        string
	Description string
	Members     map[string]actors.Agent
	Roles       map[string]string // agent ID -> role
	Leader      string            // agent ID of team leader
	CreatedAt   time.Time
	UpdatedAt   time.Time
}

// Workflow represents a sequence of collaborative tasks
type Workflow struct {
	ID          string
	Name        string
	Description string
	Stages      []*WorkflowStage
	Status      string
	CreatedAt   time.Time
	UpdatedAt   time.Time
	Metadata    map[string]interface{}
}

// WorkflowStage represents a stage in a workflow
type WorkflowStage struct {
	ID           string
	Name         string
	Description  string
	Tasks        []*proto.Task
	Assignee     string // agent ID
	Status       string
	StartedAt    time.Time
	CompletedAt  time.Time
	Dependencies []string // stage IDs
}

// Consensus represents a decision-making process
type Consensus struct {
	ID          string
	Topic       string
	Description string
	Options     []string
	Votes       map[string]string // agent ID -> option
	Status      string
	StartedAt   time.Time
	EndedAt     time.Time
	Result      string
	Metadata    map[string]interface{}
}

// Manager handles collaboration patterns
type Manager struct {
	teams     map[string]*Team
	workflows map[string]*Workflow
	consensus map[string]*Consensus
	logger    *logrus.Entry
	mutex     sync.RWMutex
}

// NewManager creates a new collaboration manager
func NewManager() *Manager {
	return &Manager{
		teams:     make(map[string]*Team),
		workflows: make(map[string]*Workflow),
		consensus: make(map[string]*Consensus),
		logger:    logrus.WithField("component", "collaboration_manager"),
	}
}

// CreateTeam creates a new team
func (m *Manager) CreateTeam(team *Team) error {
	m.mutex.Lock()
	defer m.mutex.Unlock()

	if team.ID == "" {
		team.ID = uuid.NewString()
	}

	if _, exists := m.teams[team.ID]; exists {
		return fmt.Errorf("team with ID %s already exists", team.ID)
	}

	team.CreatedAt = time.Now()
	team.UpdatedAt = time.Now()
	team.Members = make(map[string]actors.Agent)
	team.Roles = make(map[string]string)

	m.teams[team.ID] = team
	return nil
}

// AddTeamMember adds a member to a team
func (m *Manager) AddTeamMember(teamID string, agent actors.Agent, role string) error {
	m.mutex.Lock()
	defer m.mutex.Unlock()

	team, exists := m.teams[teamID]
	if !exists {
		return fmt.Errorf("team with ID %s not found", teamID)
	}

	if _, exists := team.Members[agent.GetID()]; exists {
		return fmt.Errorf("agent %s is already a member of team %s", agent.GetID(), teamID)
	}

	team.Members[agent.GetID()] = agent
	team.Roles[agent.GetID()] = role
	team.UpdatedAt = time.Now()

	return nil
}

// CreateWorkflow creates a new workflow
func (m *Manager) CreateWorkflow(workflow *Workflow) error {
	m.mutex.Lock()
	defer m.mutex.Unlock()

	if workflow.ID == "" {
		workflow.ID = uuid.NewString()
	}

	if _, exists := m.workflows[workflow.ID]; exists {
		return fmt.Errorf("workflow with ID %s already exists", workflow.ID)
	}

	workflow.CreatedAt = time.Now()
	workflow.UpdatedAt = time.Now()
	workflow.Status = "created"
	workflow.Metadata = make(map[string]interface{})

	m.workflows[workflow.ID] = workflow
	return nil
}

// AddWorkflowStage adds a stage to a workflow
func (m *Manager) AddWorkflowStage(workflowID string, stage *WorkflowStage) error {
	m.mutex.Lock()
	defer m.mutex.Unlock()

	workflow, exists := m.workflows[workflowID]
	if !exists {
		return fmt.Errorf("workflow with ID %s not found", workflowID)
	}

	if stage.ID == "" {
		stage.ID = uuid.NewString()
	}

	stage.Status = "pending"
	workflow.Stages = append(workflow.Stages, stage)
	workflow.UpdatedAt = time.Now()

	return nil
}

// StartConsensus starts a new consensus process
func (m *Manager) StartConsensus(consensus *Consensus) error {
	m.mutex.Lock()
	defer m.mutex.Unlock()

	if consensus.ID == "" {
		consensus.ID = uuid.NewString()
	}

	if _, exists := m.consensus[consensus.ID]; exists {
		return fmt.Errorf("consensus with ID %s already exists", consensus.ID)
	}

	consensus.StartedAt = time.Now()
	consensus.Status = "voting"
	consensus.Votes = make(map[string]string)
	consensus.Metadata = make(map[string]interface{})

	m.consensus[consensus.ID] = consensus
	return nil
}

// CastVote casts a vote in a consensus process
func (m *Manager) CastVote(consensusID string, agentID string, option string) error {
	m.mutex.Lock()
	defer m.mutex.Unlock()

	consensus, exists := m.consensus[consensusID]
	if !exists {
		return fmt.Errorf("consensus with ID %s not found", consensusID)
	}

	if consensus.Status != "voting" {
		return fmt.Errorf("consensus is not in voting state")
	}

	validOption := false
	for _, opt := range consensus.Options {
		if opt == option {
			validOption = true
			break
		}
	}

	if !validOption {
		return fmt.Errorf("invalid option: %s", option)
	}

	consensus.Votes[agentID] = option
	return nil
}

// FinalizeConsensus finalizes a consensus process
func (m *Manager) FinalizeConsensus(consensusID string) error {
	m.mutex.Lock()
	defer m.mutex.Unlock()

	consensus, exists := m.consensus[consensusID]
	if !exists {
		return fmt.Errorf("consensus with ID %s not found", consensusID)
	}

	if consensus.Status != "voting" {
		return fmt.Errorf("consensus is not in voting state")
	}

	// Count votes
	votes := make(map[string]int)
	for _, vote := range consensus.Votes {
		votes[vote]++
	}

	// Find winning option
	var winningOption string
	maxVotes := 0
	for option, count := range votes {
		if count > maxVotes {
			maxVotes = count
			winningOption = option
		}
	}

	consensus.Status = "completed"
	consensus.EndedAt = time.Now()
	consensus.Result = winningOption

	return nil
}

// GetTeam retrieves a team by ID
func (m *Manager) GetTeam(id string) (*Team, error) {
	m.mutex.RLock()
	defer m.mutex.RUnlock()

	team, exists := m.teams[id]
	if !exists {
		return nil, fmt.Errorf("team with ID %s not found", id)
	}

	return team, nil
}

// GetWorkflow retrieves a workflow by ID
func (m *Manager) GetWorkflow(id string) (*Workflow, error) {
	m.mutex.RLock()
	defer m.mutex.RUnlock()

	workflow, exists := m.workflows[id]
	if !exists {
		return nil, fmt.Errorf("workflow with ID %s not found", id)
	}

	return workflow, nil
}

// GetConsensus retrieves a consensus process by ID
func (m *Manager) GetConsensus(id string) (*Consensus, error) {
	m.mutex.RLock()
	defer m.mutex.RUnlock()

	consensus, exists := m.consensus[id]
	if !exists {
		return nil, fmt.Errorf("consensus with ID %s not found", id)
	}

	return consensus, nil
}

// UpdateWorkflowStage updates the status of a workflow stage
func (m *Manager) UpdateWorkflowStage(workflowID string, stageID string, status string) error {
	m.mutex.Lock()
	defer m.mutex.Unlock()

	workflow, exists := m.workflows[workflowID]
	if !exists {
		return fmt.Errorf("workflow with ID %s not found", workflowID)
	}

	for _, stage := range workflow.Stages {
		if stage.ID == stageID {
			stage.Status = status
			if status == "completed" {
				stage.CompletedAt = time.Now()
			}
			workflow.UpdatedAt = time.Now()
			return nil
		}
	}

	return fmt.Errorf("stage with ID %s not found in workflow %s", stageID, workflowID)
}

// GetTeamMembers returns all members of a team
func (m *Manager) GetTeamMembers(teamID string) ([]actors.Agent, error) {
	m.mutex.RLock()
	defer m.mutex.RUnlock()

	team, exists := m.teams[teamID]
	if !exists {
		return nil, fmt.Errorf("team with ID %s not found", teamID)
	}

	members := make([]actors.Agent, 0, len(team.Members))
	for _, member := range team.Members {
		members = append(members, member)
	}

	return members, nil
}

// GetTeamsByMember returns all teams that an agent is a member of
func (m *Manager) GetTeamsByMember(agentID string) []*Team {
	m.mutex.RLock()
	defer m.mutex.RUnlock()

	var teams []*Team
	for _, team := range m.teams {
		if _, exists := team.Members[agentID]; exists {
			teams = append(teams, team)
		}
	}

	return teams
}

// GetActiveWorkflows returns all active workflows
func (m *Manager) GetActiveWorkflows() []*Workflow {
	m.mutex.RLock()
	defer m.mutex.RUnlock()

	var workflows []*Workflow
	for _, workflow := range m.workflows {
		if workflow.Status != "completed" && workflow.Status != "cancelled" {
			workflows = append(workflows, workflow)
		}
	}

	return workflows
}

// GetActiveConsensus returns all active consensus processes
func (m *Manager) GetActiveConsensus() []*Consensus {
	m.mutex.RLock()
	defer m.mutex.RUnlock()

	var processes []*Consensus
	for _, consensus := range m.consensus {
		if consensus.Status == "voting" {
			processes = append(processes, consensus)
		}
	}

	return processes
}
